/* A library of methods for the Object object
 *
 * Copyright (c) 2009 Seth Wessitsh (seth@wessitsh.com)
 * Licensed under the MIT (MIT-LICENSE.txt) licence.
 */

 if (typeof(Object.typeName) == "undefined") {
	/**
		@interface {
			description:
				"Returns the type name of the argument, this is different from typeof only
				in that it will return `array' if the argument is an instance of an array
				instead of `object'. ",

			parameters: [
				{name: "o", type: "any", 
					description: "Anything to determine the type name of"}],

			returns: { type: "string",
				desription:
					"The value of typeof if the argument o is not an object,
					if `o' is an object and an instance of Array, then `array' is returned
					otherwise `object' is returned."}
		}
	 */
	Object.typeName = function typeName(o) { return typeof(o) == "object" ? (o instanceof Array ? "array" : "object") : typeof(o); };
} else {
	throw new Error("Static method Object.typeName already exists.");
}

if (typeof(Object.copy) == "undefined") {
	/**
		@interface {
			description: "
				Create a deep copy of the `obj' argument.

				Note: this function cannot handle circularly referenced arrays/objects. ",

			parameters: [
				{name: "obj", type: "object",
					description: "The object to copy"} ]

			returns: { type: "object",
				description: "A deep copy of the `obj' argument." }
		}
	*/
	Object.copy = function(obj) {
		var oObj = {};

		for (var key in obj) {
			if (!obj.hasOwnProperty(key)) {
				continue;
			}

			switch(Object.typeName(obj[key])) {
				case "array":
					oObj[key] = Array.copy(obj[key]);
				break;
				case "object":
					oObj[key] = Object.copy(obj[key]);
				break;
				default:
					oObj[key] = obj[key];
			}
		}

		return oObj;
	};
} else {
	throw new Error("Static method Object.keys already exists.");
}

if (typeof(Object.keys) == "undefined") {
	/**
		@interface {
			description: 
				"Returns an array of the property names in an object",

			parameters: [
				{name: "obj", type: ["object", "array"],
					description: "The object to get the property names for"} ]

			returns: { type: "array",
				description: "An array of the property names (keys) in an object" }
		}
	*/
	Object.keys = function keys(obj) {
		var out = [];

		for (var key in obj) {
			if (obj.hasOwnProperty(key)) {
				out[out.length] = key;
			}
		}

		return out;
	};
} else {
	throw new Error("Static method Object.keys already exists.");
}

if (typeof(Object.values) == "undefined") {
	/**
		@interface {
			description: 
				"Returns an array of the values for each property in an object",

			parameters: [
				{name: "obj", type: ["object", "array"],
					description: "The object to get the value of each property for"} ]

			returns: { type: "array",
				description: "An array of the values of each property in an object" }
		}
	*/
	Object.values = function values(obj) {
		var out = [];

		for (var key in obj) {
			if (obj.hasOwnProperty(key)) {
				out[out.length] = obj[key];
			}
		}

		return out;
	};
} else {
	throw new Error("Static method Object.values already exists.");
}

if (typeof(Object.isA) == "undefined") {
	/**
		@interface {
			description: 
				"Determine whether `obj' is an instance of `classType'",

			parameters: [
				{name: "obj", type: "any",
					description: "A value to test the type of"} 

				{name: "classType", type: "function",
					description: "The name of the class which `obj' may be a member of"} ]

			returns: { type: "boolean",
				description: "True if `obj' is an instance of `classType', false otherwise." }
		}
	*/
	Object.isA = function isA(obj, classType) {
		switch (classType) {
			case Array:
				return Object.prototype.toString.call(obj) === "[object Array]";
			case Function:
				return Object.prototype.toString.call(obj) === "[object Function]";
		}

		for (var prop in classType.prototype) {
			if (typeof(obj[prop]) == "undefined") {
				return false;
			}
		}

		return true;
	};
} else {
	throw new Error("Static method Object.isA already exists.");
}

if (typeof(Object.isArray) == "undefined") {
	/**
		@interface {
			description: 
				"Check whether `obj' is an array or not",

			parameters: [
				{name: "obj", type: "any",
					description: "A value to test the type of"} ]

			returns: { type: "boolean",
				description: "true if the `obj' is an instance of an array, false otherwise." }
		}
	*/
	Object.isArray = function isArray(obj) {
		return Object.prototype.toString.call(obj) === "[object Array]";
	};
} else {
	throw new Error("Static method Object.isArray already exists.");
}

if (typeof(Object.isFunction) == "undefined") {
	/**
		@interface {
			description: 
				"Check whether `obj' is an function or not",

			parameters: [
				{name: "obj", type: "any",
					description: "A value to test the type of"} ]

			returns: { type: "boolean",
				description: "true if the `obj' is an instance of an function, false otherwise." }
		}
	*/
	Object.isFunction = function isFunction(obj) {
		return Object.prototype.toString.call(obj) === "[object Function]";
	};
} else {
	throw new Error("Static method Object.isFunction already exists.");
}

if (typeof(Object.toStringR) == "undefined") {
	/**
		@interface {
			description: 
				"A function to recursively create a JSONesque string representation of a
				javascript object for human consumption (i.e., formatted with newlines/tabs). 
				This method cannot handle circularly referenced objects and will result 
				in an infinite loop if provided with one.",

			parameters: [
				{name: "obj", type: ["object", "array"],
					description: "An object or array to represent as a string."},

				{name: "showArrayIndicies", type: "boolean",
					description: 
						"Whether (true) or not (false) to display the indicies of arrays as though
						they were properties of the array."}]

			returns: { type: "string",
				description: 
					"A JSONesque (i.e., not completely conformant to the JSON standard)
					string representation of the `obj' argument." }
		}
	*/
	Object.toStringR = function toStringR(obj, showArrayIndicies) {
		var sai = showArrayIndicies === undefined ? true : !!showArrayIndicies,
			ia = Object.isArray(obj),
			idx = (!ia || ia && sai) ? "%s: " : "",
			lbs, rbs, output = "", ha = false;
		
		if (ia) { lbs = "["; rbs = "]"; }
		else { lbs = "{"; rbs = "}"; }

		if (!(obj instanceof Object) && typeof(obj) != "function") {
			return obj+"";
		}

		for (var attr in obj) {
			ha = true;
			if (typeof(obj[attr]) == "object") {
				output += idx.replace("%s", attr) + Object.toStringR(obj[attr], sai) + ",\n";
			} else {
				var quote = typeof(obj[attr]) == "string" ? "\"" : "";
				output += idx.replace("%s", attr) + quote + obj[attr] + quote + ",\n";
			}
		}

		if (ha) {
			output = lbs + "\n" + 
				output.substring(0,output.length-2)
					.replace(new RegExp("^"), "\t")
					.replace(new RegExp("\n", "g"), "\n\t") + 
				"\n" + rbs;
		} else {
			output = lbs + rbs;
		}

		return output;
	};
} else {
	throw new Error("Static method Object.toStringR already exists.");
}

if (typeof(Object.except) == "undefined") {
	/**
		@interface {
			description: 
				"Create a new object with only the properties which occur in `a' but not in `b'.",

			parameters: [
				{name: "a", type: "object",
					description: 
						"An object whos properties will be added to the output object so long as
						they do not occur in `b'" } 

				{name: "b", type: "object",
					description: 
						"An object whos properties will be used to mask off the 
						properties of `a'" } ]

			returns: { type: "object",
				description: "A new object with all properties in `a' which do not occur in `b'" }
		}
	*/
	Object.except = function except(a,b) {
		if (arguments.length > 2) {
			var temp = Array.shift(arguments);
			b = Object.except.apply(this, arguments);
			a = temp;
		}

		var retVal = {};

		for (var prop in a) {
			if (!(prop in b)) { retVal[prop] = a[prop]; }
		}

		return retVal;
	};
} else {
	throw new Error("Static method Object.except already exists.");
}

if (typeof(Object.union) == "undefined") {
	/**
		@interface {
			description: 
				"Create a new object with the unique properties from `a' and `b'.  
				
				If the property occurs in both `a' and `b' then the value for the property in 
				`a' will be used.",

			parameters: [
				{name: "a", type: "object",
					description: 
						"An object whos properties will be added to the output" } 

				{name: "b", type: "object",
					description: 
						"An object whos properties will be added to the output so long as they
						do not occur in `a'" } ]

			returns: { type: "object",
				description: 
					"A new object with all properties in `a' and those properties in `b' which
					do not occur in `a'" }
		}
	*/	
	Object.union = function union(a,b) {
		if (arguments.length > 2) {
			var temp = Array.shift(arguments);
			b = Object.union.apply(this, arguments);
			a = temp;
		}

		var retVal = {}, prop;

		for (prop in a) {
			if (a.hasOwnProperty(prop)) {
				retVal[prop] = a[prop];
			}
		}

		for (prop in b) {
			if (b.hasOwnProperty(prop) && !(prop in retVal)) { 
				retVal[prop] = b[prop]; 
			}
		}
		
		return retVal;
	};
} else {
	throw new Error("Static method Object.union already exists.");
}

if (typeof(Object.intersect) == "undefined") {
	/**
		@interface {
			description: 
				"Create a new object with only the properties which occur in both `a' and `b'.
				
				This retains the values of the properties from `a'",

			parameters: [
				{name: "a", type: "object",
					description: 
						"An object whos properties will be added to the output object so long as
						they also occur in `b'." } 

				{name: "b", type: "object",
					description: 
						"An object whos properties will be added to the output object so long as
						they also occur in `a'.  Note: the values will be from `a'." } ]

			returns: { type: "object",
				description: 
					"A new object with all properties in `a' which also occur in `b'.  The values
					of which will be those found in `a'." }
		}
	*/
	Object.intersect = function intersect(a,b) {
		if (arguments.length > 2) {
			var temp = Array.shift(arguments);
			b = Object.intersect.apply(this, arguments);
			a = temp;
		}

		var retVal = {};

		for (var prop in a) {
			if (a.hasOwnProperty(prop) && prop in b) { 
				retVal[prop] = a[prop]; 
			}
		}

		return retVal;
	};
} else {
	throw new Error("Static method Object.intersect already exists.");
}